package com.sencorsta.ids.core.log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import com.sencorsta.ids.core.configure.GlobalConfigure;
import com.sencorsta.ids.core.configure.SysConfig;
import com.sencorsta.ids.core.function.FunctionSystem;
import com.sencorsta.utils.date.DateUtil;
import org.apache.log4j.Logger;


/**
 * 日志输出类
 * 
 * @author ICe
 */
public final class Out {
	public static Logger logger = Logger.getLogger(Out.class);
	private static class OutPrintStream extends PrintStream {
		private static final boolean CONSOLE = SysConfig.getInstance().getBoolean("log.console", true);
		private static final SimpleDateFormat FORMAT_DAY = new SimpleDateFormat("yyyy-MM-dd");

		private PrintStream fps;
		private String fileName;
		private String filePath;
		private boolean newLog = true;
		private OutPrintStream(PrintStream print, String fs) {
			super(print);
			this.fileName = fs;
			Calendar calendar = Calendar.getInstance();
			calendar.set(Calendar.HOUR_OF_DAY, 23);
			calendar.set(Calendar.MINUTE, 59);
			calendar.set(Calendar.SECOND, 59);
			FunctionSystem.addFixedRateJob(new Runnable() {
				@Override
				public void run() {
					newLog = true;
				}
			}, calendar.getTimeInMillis() - System.currentTimeMillis(), GlobalConfigure.TIME_DAY);
		}

		public void write(byte buf[], int off, int len) {
			newLog();
			if (CONSOLE) {
				super.write(buf, off, len);
			}
			fps.write(buf, off, len);
		}

		public void flush() {
			if (CONSOLE) {
				super.flush();
			}
			fps.flush();
		}

		private String getFilePath() {
			String day = FORMAT_DAY.format(new Date());
			return new StringBuilder(GlobalConfigure.DIR_LOG).append(File.separator).append(day.substring(0, 7))
					.append(File.separator).append(day).append(File.separator).append(fileName).toString();
		}

		public void newLog() {
			if (newLog && !getFilePath().equals(filePath)) {
				try {
					if (fps != null) {
						fps.close();
					}
					newLog = false;
					filePath = getFilePath();
					File log = new File(filePath);
					log.getParentFile().mkdirs();
					fps = new PrintStream(new FileOutputStream(log, true));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	private static Level LEVEL = Level.TRACE;
	private static boolean moreDetails = false;
	/** 设置 */
	private static boolean setting;

	public static synchronized void setting() {
		if (setting)
			return;
		setting = true;
		LEVEL = Level.valueOf(SysConfig.getInstance().get("log.level", "INFO").toUpperCase());
		moreDetails=SysConfig.getInstance().getBoolean("log.moredetails", false);
		Out.info("当前日志等级:" + LEVEL.name());
		File dir = new File(GlobalConfigure.DIR_LOG);
		if (!dir.exists() || !dir.isDirectory()) {
			dir.mkdir();
		}
		System.setOut(new OutPrintStream(System.out, "system.out.log"));
		System.setErr(new OutPrintStream(System.err, "system.out.log"));
		System.setErr(new OutPrintStream(System.err, "system.err.log"));
		Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Log log = __QUEUE__.take();
						switch (log.type) {
						case TRACE: {
							System.out.println(log("TRACE", log));
//							logger.trace(log("TRACE", log.args));
							break;
						}
						case DEBUG: {
							System.out.println(log("DEBUG", log));
//							logger.debug(log("DEBUG", log.args));
							break;
						}
						case INFO: {
							System.out.println(log("INFO", log));
//							logger.info(log("INFO", log.args));
							break;
						}
						case WARN: {
							System.err.println(log("WARN", log));
//							logger.warn(log("WARN", log.args));
							break;
						}
						case ERROR: {
							System.err.println(log("ERROR", log));
//							logger.error(log("ERROR", log.args));
							break;
						}
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		});
		thread.setName("fs-log");
		thread.start();

	}

	public static void setLevel(Level level) {
		LEVEL = level;
	}

	private static class Log {
		Level type;
		Object[] args;
		String threadName="";
		String codePosion="";

		Log(Level type, Object... args) {
			this.args = args;
			this.type = type;
			if (moreDetails) {
				this.threadName=" @ "+Thread.currentThread().getName()+" ";
				String classNameFull = Thread.currentThread().getStackTrace()[4].getClassName();
				String className = classNameFull.substring(classNameFull.lastIndexOf(".") + 1);
				int lineNum = Thread.currentThread().getStackTrace()[4].getLineNumber();
				this.codePosion = "(" + className + ".java:" + lineNum + ")";
			}
		}
	}

	private static final BlockingQueue<Log> __QUEUE__ = new LinkedBlockingQueue<Log>();

	private static void put(Level type, Object... args) {
		if (__QUEUE__.size() >= 100000) {
			return;
		}
		__QUEUE__.add(new Log(type, args));
	}

	public static boolean isEnable(Level level) {
		return LEVEL.compareTo(level) <= 0;
	}

	public static boolean isEnableTrace() {
		return isEnable(Level.TRACE);
	}

	public static boolean isEnableDebug() {
		return isEnable(Level.DEBUG);
	}

	public static boolean isEnableInfo() {
		return isEnable(Level.INFO);
	}

	private static String log(String type, Log log) {
		Object[] args=log.args;

		StringBuilder builder = new StringBuilder();
		builder.append(DateUtil.getTime(DateUtil.F_FULL));
		builder.append(log.threadName);
		builder.append(log.codePosion);
		builder.append(" [").append(type).append("]");
		builder.append(" : ");
		for (Object object : args) {
			if (object instanceof Throwable) {
				((Throwable) object).printStackTrace();
				builder.append(((Throwable) object).getMessage());
			} else {
				builder.append(object);
			}
		}
		return builder.toString();
	}

	public static void trace(Object... args) {
		if (isEnableTrace()) {
			put(Level.TRACE, args);
		}
	}

	public static void debug(Object... args) {
		if (isEnableDebug()) {
			put(Level.DEBUG, args);
		}
	}

	public static void info(Object... args) {
		if (isEnableInfo()) {
			put(Level.INFO, args);
		}
	}

	public static void warn(Object... args) {
		put(Level.WARN, args);
	}

	public static void error(Object... args) {
		put(Level.ERROR, args);
	}

	/**
	 * 红字输出
	 */
	public static void red(Object... args) {
		System.err.println(log("RED", new Log(Level.TRACE,args)));
	}

}
